
package client

import (
	"encoding/json"
	"time"
	"adai.design/homemaster/log"
	"errors"
	"os/exec"
	"adai.design/homemaster/modules"
	"github.com/gorilla/websocket"
)

const (
	Disconnected = iota
	VerifyStart
	VerifyFinished
)

const (
	notifyDisable = iota
	notifyEnable
)

type Client struct {
	conn 	*websocket.Conn

	state 	int

	// 发送以及接收的数据
	send 	chan *Message
	read 	chan *Message

	// 最后一次心跳时间
	lasthb  time.Time

	notify  int
	remote  string
}

func (c *Client) writeMessage(msg *Message) error {
	if c.state == VerifyFinished {
		c.send <- msg
	}
	return nil
}

func (c *Client) write(path, method string, data []byte) error {
	msg := &Message {
		Path: path,
		Method: method,
		Data: data,
	}

	if c.state != VerifyFinished {
		buf, _ := json.Marshal(msg)
		err := c.conn.WriteMessage(websocket.BinaryMessage, buf)
		return err
	}

	c.send <- msg
	return nil
}

func (c *Client) readMessage() (*Message, error) {
	_, data, err := c.conn.ReadMessage()
	if err != nil {
		return nil, err
	}

	var message Message
	err = json.Unmarshal(data, &message)
	if err != nil {
		return nil, err
	}
	log.Info("read <- %s", string(data))
	return &message, nil
}

func (c *Client) dialTimeout(timeout time.Duration) error {
	conn, _, err := websocket.DefaultDialer.Dial(c.remote, nil)
	//conn, err := tls.DialWithDialer(&net.Dialer{Timeout: timeout,}, "tcp", c.remote, nil)
	if err != nil {
		log.Error("dial with timeout: %s", err)
		return err
	}

	c.conn = conn
	c.state = VerifyStart
	return nil
}

type loginInfo struct {
	UUID 	string		`json:"uuid"`
	Key 	string		`json:"key"`
	Date 	string		`json:"date"`
}

const (
	loginAckOk	= "ok"
	logAckFailed = "invalid"
)

func (c *Client) disconnect() {
	c.conn.Close()
	c.state = Disconnected
}

// 设备登录认证
func (c *Client) verify(timeout time.Duration) error {
	c.conn.SetWriteDeadline(time.Now().Add(timeout))
	c.conn.SetWriteDeadline(time.Now().Add(timeout))

	login := &loginInfo{
		UUID: registration.UUID,
		Key: registration.PrivateKey,
		Date: time.Now().Format("2006/01/02 15:04:05.99"),
	}

	data, _ := json.Marshal(login)
	err := c.write(MsgPathLogin, MsgMethodPost, data)
	if err != nil {
		c.disconnect()
		log.Debug("%s", err)
		return err
	}

	ack, err := c.readMessage()
	if err != nil {
		c.disconnect()
		log.Debug("%s", err)
		return err
	}

	if ack.State != loginAckOk || ack.Path != MsgPathLogin {
		c.disconnect()
		return errors.New("verify failed")
	}


	var loginDate = struct {
		Date 	string 		`json:"date"`
	}{}
	json.Unmarshal(ack.Data, &loginDate)

	log.Debug("verify finished date(%s)", "'" + loginDate.Date + "'")

	cmd := exec.Command("date", "-s", loginDate.Date)
	log.Info("command: %v", cmd.Args)
	cmd.Start()
	cmd.Wait()
	modules.SetClockAutoBrightness(300)

	c.state = VerifyFinished
	return nil
}

// 消息处理
func (c *Client) handle(msg *Message) error {
	if h, ok := messageHandleMap[msg.Path]; ok {
		return h.handle(c, msg)
	}
	return errors.New("unknown message type")
}


// 登录成功之后向服务器主动上传家庭与版本信息
func (c *Client) sync() error {
	data := map[string]interface{} {
		"home": registration.HomeId,
		"firmware": "0.1.1",
		"date": time.Now().Format("2006/01/02 15:04:05.99"),
	}
	buf, _ := json.Marshal(data)
	c.write(MsgPathBind, MsgMethodPut, buf);
	return nil
}

// 信息传输
func (c *Client) transfer(heartbeat, timeout time.Duration) error {
	c.send = make(chan *Message, 10)
	c.read = make(chan *Message, 10)

	c.sync()

	c.conn.SetPongHandler(func(appData string) error {
		c.conn.SetReadDeadline(time.Now().Add(timeout))
		return nil
	})

	// 读取消息
	go func(c *Client, timeout time.Duration) {
		for {
			c.conn.SetReadDeadline(time.Now().Add(timeout))
			msg, err := c.readMessage()
			if err != nil {
				log.Error("read err: %s", err)
				c.disconnect()
				close(c.read)
				return
			}
			c.read <- msg
		}
	}(c, timeout)

	// 发送消息
	go func(c *Client, timeout time.Duration) {
		for msg := range c.send {
			c.conn.SetWriteDeadline(time.Now().Add(timeout))
			buf, _ := json.Marshal(msg)
			log.Info("send ->  %s", string(buf))
			err := c.conn.WriteMessage(websocket.BinaryMessage, buf)
			if err != nil {
				log.Error("send err: %s", err)
				c.disconnect()
				return
			}
		}
	}(c, timeout)

	eventLoop: for {
		select {
		case msg, ok := <- c.read:
			if !ok {
				break eventLoop
			}
			c.handle(msg)

		case <- time.After(heartbeat):
			c.conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(timeout))
		}
	}

	c.notify = notifyDisable
	c.state = Disconnected
	close(c.send)
	c.disconnect()
	log.Debug("disconnected")
	return nil
}

func (c *Client) start() error {
	go func() {
		for {
			switch c.state {
			case Disconnected:
				err := c.dialTimeout(dialTimeout)
				if err != nil {
					time.Sleep(reconnectInterval)
				}
			case VerifyStart:
				c.verify(verifyTimeout)

			case VerifyFinished:
				c.transfer(heartbeatDuration, timeoutDuration)
			}
		}
	}()
	return nil
}

func (c *Client) stop() error {
	return nil
}

const (
	heartbeatDuration = time.Second * 5
	timeoutDuration = time.Second * 16
	verifyTimeout = time.Second * 5
	dialTimeout = time.Second * 5
	reconnectInterval = time.Second * 3
)

//var client = Client{remote: "adai.design:6666", state: Disconnected}
//var client = Client{remote: "ws://192.168.199.155:6666/device", state: Disconnected}
var client = Client{remote: "ws://adai.design:6666/device", state: Disconnected}

func Start() {
	client.start()
	notifyStart(&client)
}

func Stop() {
	client.stop()
}








